namespace SharpGraphT;

public static class GraphExtension {
    public static bool AddGraph<TV, TE>(this IGraph<TV, TE> destination,
        IGraph<TV, TE> source) where TE : class, new() =>
        destination.AddAllVertices(source.VertexSet) |
        destination.AddAllEdges(source, source.EdgeSet);

    public static bool AddAllVertices<TV, TE>(this IGraph<TV, TE> destination,
        IEnumerable<TV> vertices) where TE : class, new() =>
        vertices.Aggregate(false, (b, v) => b | destination.AddVertex(v));

    public static bool AddAllEdges<TV, TE>(this IGraph<TV, TE> destination,
        IGraph<TV, TE> source, IEnumerable<TE> edges) where TE : class, new() =>
        edges.Aggregate(false, (b, e) => {
            var s = source.GetEdgeSource(e);
            var t = source.GetEdgeTarget(e);
            destination.AddVertex(s);
            destination.AddVertex(t);
            return b | destination.AddEdge(s, t, e);
        });

    public static TE AddEdgeWithVertices<TV, TE>(this IGraph<TV, TE> g,
        TV sourceVertex, TV targetVertex) where TE : class, new() {
        g.AddVertex(sourceVertex);
        g.AddVertex(targetVertex);
        return g.AddEdge(sourceVertex, targetVertex);
    }

    public static TE AddEdgeWithVertices<TV, TE>(this IGraph<TV, TE> g,
        TV sourceVertex, TV targetVertex, double weight)
        where TE : class, new() {
        g.AddVertex(sourceVertex);
        g.AddVertex(targetVertex);

        return g.AddEdge(sourceVertex, targetVertex, weight);
    }

    public static TE AddEdge<TV, TE>(this IGraph<TV, TE> g, TV sourceVertex,
        TV targetVertex, double weight) where TE : class, new() {
        var edgeSupplier = g.EdgeSupplier;
        if (edgeSupplier == null) {
            throw new InvalidOperationException(
                "Graph contains no edge supplier");
        }

        var e = edgeSupplier();
        if (!g.AddEdge(sourceVertex, targetVertex, e)) {
            return null;
        }

        g.SetEdgeWeight(e, weight);
        return e;
    }

    public static bool RemoveVertexAndPreserveConnectivity<TV, TE>(
        this IGraph<TV, TE> graph, TV vertex) where TE : class, new() {
        if (!graph.ContainsVertex(vertex)) {
            return false;
        }

        if (graph.VertexHasPredecessors(vertex)) {
            var predecessors = graph.PredecessorListOf(vertex);
            var successors = graph.SuccessorListOf(vertex);

            foreach (var predecessor in predecessors) {
                graph.AddOutgoingEdges(predecessor, successors);
            }
        }

        graph.RemoveVertex(vertex);
        return true;
    }

    public static bool VertexHasPredecessors<TV, TE>(this IGraph<TV, TE> graph,
        TV vertex) where TE : class, new() =>
        graph.IncomingEdgesOf(vertex).Any();

    public static IList<TV> PredecessorListOf<TV, TE>(this IGraph<TV, TE> g,
        TV vertex) where TE : class, new() =>
        g.Iterables.IncomingEdgesOf(vertex)
            .Select(e => g.GetOppositeVertex(e, vertex)).ToList();

    public static TV GetOppositeVertex<TV, TE>(this IGraph<TV, TE> g, TE e,
        TV v) where TE : class, new() {
        var source = g.GetEdgeSource(e);
        var target = g.GetEdgeTarget(e);
        if (v.Equals(source)) {
            return target;
        }

        if (v.Equals(target)) {
            return source;
        }

        throw new ArgumentException($"no such vertex: {v}");
    }

    public static IList<TV>
        SuccessorListOf<TV, TE>(this IGraph<TV, TE> g, TV vertex)
        where TE : class, new() =>
        g.Iterables.OutgoingEdgesOf(vertex)
            .Select(e => g.GetOppositeVertex(e, vertex)).ToList();

    public static void AddOutgoingEdges<TV, TE>(this IGraph<TV, TE> graph,
        TV source, IEnumerable<TV> targets) where TE : class, new() {
        if (!graph.ContainsVertex(source)) {
            graph.AddVertex(source);
        }

        foreach (var target in targets) {
            if (!graph.ContainsVertex(target)) {
                graph.AddVertex(target);
            }

            graph.AddEdge(source, target);
        }
    }

    public static void AddIncomingEdges<TV, TE>(this IGraph<TV, TE> graph,
        TV target, IEnumerable<TV> sources) where TE : class, new() {
        if (!graph.ContainsVertex(target)) {
            graph.AddVertex(target);
        }

        foreach (var source in sources) {
            if (!graph.ContainsVertex(source)) {
                graph.AddVertex(source);
            }

            graph.AddEdge(source, target);
        }
    }

    public static bool VertexHasSuccessors<TV, TE>(this IGraph<TV, TE> graph,
        TV vertex) where TE : class, new() =>
        graph.OutgoingEdgesOf(vertex).Any();

    public static IReadOnlySet<TV> NeighborSetOf<TV, TE>(this IGraph<TV, TE> g,
        TV vertex) where TE : class, new() =>
        g.Iterables.EdgesOf(vertex).Select(e => g.GetOppositeVertex(e, vertex))
            .ToHashSet();
}